/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/
#pragma once

#include <stdint.h>
#include <iterator>

#include "kcommon.h"

#define ENCODING(name) kEncoding ## name
#define SET_DEFAULT_ENCODING(name) template<> class kDefaultEncoding<ENCODING(name)::size> : public ENCODING(name) { }

typedef char kchar8_t;
typedef wchar_t kchar16_t;
typedef uint32_t kchar32_t;

template<typename E1, typename E2> class kEncodingConverter {
public:
	template<typename Iter1, typename Iter2> static inline Iter2 convert(Iter1 begin, Iter1 end, Iter2 dest) {
		for (; begin != end; ) {
			E2::encode(dest, E1::decode(begin));
		}

		return dest;
	}
	template<typename T1, typename T2> static inline void convert(const std::basic_string<T1>& source, std::basic_string<T2>& dest) {
		convert(source.begin(), source.end(), std::back_inserter(dest));
	}
};
template<typename E> class kEncodingConverter<E, E> {
public:
	template<typename Iter1, typename Iter2> static inline Iter2 convert(Iter1 begin, Iter1 end, Iter2 dest) {
		return std::copy(begin, end, dest);
	}
	template<typename T1, typename T2> static inline void convert(const std::basic_string<T1>& source, std::basic_string<T2>& dest) {
		dest = source;
	}
};

template<size_t S = sizeof(tchar)> class kDefaultEncoding {};
template<typename T = tchar> class kEncoding : public kDefaultEncoding<sizeof(T)> {};

template<typename E> class kEncodingImpl {
public:
	typedef E charType;
	static const size_t size = sizeof(E);
	inline static bool isBOM(charType c, bool& swapEndian) {
		swapEndian = false;
		return false;
	}
	inline static void swapEndian(charType& c) {
		uint8_t* bytes = (uint8_t*)&c;
		for (size_t i = 0; i < size/2; i++) {
			size_t j = size-i;
			std::swap(bytes[i], bytes[j]);
		}
	}
};

class kEncodingUTF32 : public kEncodingImpl<kchar32_t> {
public:
	template<typename Iter> static uint32_t decode(Iter& i) {
		static_assert(sizeof(*i) == size, "Invalid input iterator");

		return (uint32_t)*i++;
	}
	template<typename Iter> static void encode(Iter& i, uint32_t codepoint) {
		*i++ = (charType)codepoint;
	}
	static const charType* const whitespace;
	inline static bool isBOM(charType c, bool& swapEndian) {
		swapEndian = (c == 0xFFFE0000u);
		return swapEndian || (c == 0x0000FEFFu);
	}
};

class kEncodingUTF16 : public kEncodingImpl<kchar16_t> {
public:
	template<typename Iter> static uint32_t decode(Iter& i) {
		static_assert(sizeof(*i) == size, "Invalid input iterator");

		uint32_t a = (uint16_t)(charType)*i++;
		if (a < 0xD800 || a >= 0xDC00)
			return a;
		
		uint32_t b = (uint16_t)(charType)*i;
		if (b < 0xDC00 || b >= 0xE000)
			return a;

		i++;

		return ((a - 0xD800) << 10) | (b - 0xDC00);
	}
	template<typename Iter> static void encode(Iter& i, uint32_t codepoint) {
		if (codepoint < 0x10000) {
			*i++ = (charType)(uint16_t)codepoint;
		} else {
			codepoint -= 0x10000;
			*i++ = (charType)(uint16_t)(0xD800 + (codepoint >> 10));
			*i++ = (charType)(uint16_t)(0xDC00 + (codepoint & 0x3FF));
		}
	}
	static const charType* const whitespace;
	inline static bool isBOM(charType c, bool& swapEndian) {
		swapEndian = (c == L'\xFFFE');
		return swapEndian || (c == L'\xFEFF');
	}
};

class kEncodingUTF8 : public kEncodingImpl<kchar8_t> {
public:
	template<typename Iter> static uint32_t decode(Iter& i) {
		static_assert(sizeof(*i) == size, "Invalid input iterator");

		uint32_t a = (uint8_t)(charType)*i++;
		if (a < 0xC0)
			return a;
		
		uint32_t b = (uint8_t)(charType)*i;
		if (b < 0x80)
			return a;
		i++;

		if (a < 0xE0)
			return ((a - 0xC0) << 6) | (b - 0x80);

		uint32_t c = (uint8_t)(charType)*i;
		if (c < 0x80)
			return a;
		i++;

		if (a < 0xF0)
			return ((a - 0xE0) << 12) | ((b - 0x80) << 6) | (c - 0x80);

		uint32_t d = (uint8_t)(charType)*i;
		if (d < 0x80)
			return a;
		i++;

		if (a < 0xF8)
			return ((a - 0xF0) << 18) | ((b - 0x80) << 12) | ((c - 0x80) << 6) | (d - 0x80);

		uint32_t e = (uint8_t)(charType)*i;
		if (e < 0x80)
			return a;
		i++;

		if (a < 0xFC)
			return ((a - 0xF8) << 24) | ((b - 0x80) << 18) | ((c - 0x80) << 12) | ((d - 0x80) << 6) | (e - 0x80);

		uint32_t f = (uint8_t)(charType)*i;
		if (f < 0x80)
			return a;
		i++;

		if (a < 0xFE)
			return ((a - 0xFC) << 30) | ((b - 0x80) << 24) | ((c - 0x80) << 18) | ((d - 0x80) << 12) | ((e - 0x80) << 6) | (f - 0x80);

		return a;
	}
	template<typename Iter> static void encode(Iter& i, uint32_t codepoint) {
		if (codepoint < 0x80) {
			*i++ = (charType)(uint8_t)codepoint;
		} else if (codepoint < 0x800) {
			*i++ = (charType)(uint8_t)(0xC0 + (codepoint >> 6));
			*i++ = (charType)(uint8_t)(0x80 + (codepoint & 0x3F));
		} else if (codepoint < 0x10000) {
			*i++ = (charType)(uint8_t)(0xE0 + (codepoint >> 12));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 6) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + (codepoint & 0x3F));
		} else if (codepoint < 0x200000) {
			*i++ = (charType)(uint8_t)(0xF0 + (codepoint >> 18));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 12) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 6) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + (codepoint & 0x3F));
		} else if (codepoint < 0x4000000) {
			*i++ = (charType)(uint8_t)(0xF8 + (codepoint >> 24));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 18) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 12) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 6) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + (codepoint & 0x3F));
		} else {
			*i++ = (charType)(uint8_t)(0xFC + (codepoint >> 30));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 24) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 18) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 12) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + ((codepoint >> 6) & 0x3F));
			*i++ = (charType)(uint8_t)(0x80 + (codepoint & 0x3F));
		}
	}
	static const charType* const whitespace;
};

class kEncodingANSI : public kEncodingImpl<kchar8_t> {
public:
	template<typename Iter> static uint32_t decode(Iter& i) {
		static_assert(sizeof(*i) == size, "Invalid input iterator");

		return (uint8_t)(charType)*i++;
	}
	template<typename Iter> static void encode(Iter& i, uint32_t codepoint) {
		*i++ = (charType)(uint8_t)codepoint;
	}
	static const charType* const whitespace;
};

class kEncodingUCS2 : public kEncodingImpl<kchar16_t> {
public:
	template<typename Iter> static uint32_t decode(Iter& i) {
		static_assert(sizeof(*i) == size, "Invalid input iterator");

		return (uint16_t)(charType)*i++;
	}
	template<typename Iter> static void encode(Iter& i, uint32_t codepoint) {
		*i++ = (charType)(uint16_t)codepoint;
	}
	static const charType* const whitespace;
	inline static bool isBOM(charType c, bool& swapEndian) {
		swapEndian = (c == L'\xFFFE');
		return swapEndian || (c == L'\xFEFF');
	}
};

SET_DEFAULT_ENCODING(UTF8);
SET_DEFAULT_ENCODING(UTF16);
SET_DEFAULT_ENCODING(UTF32);
